package com.webrtc.demo.call

import android.util.Log
import io.socket.client.Ack
import io.socket.client.Manager
import io.socket.client.Socket
import io.socket.engineio.client.Transport
import org.json.JSONObject
import org.webrtc.IceCandidate
import java.net.URI
import java.util.*


object RTCSignalClient {

    private lateinit var socket: Socket

    private lateinit var myid: String

    private const val TAG = "RTCSignalClient"


    val WS_URLS = arrayOf(
        "http://18.162.220.173:3131",
        "http://18.162.223.137:3131"
    )

    private const val SIGNAL_TYPE_OFFER = "OFFER"
    private const val SIGNAL_TYPE_ANSWER = "ANSWER"
    private const val SIGNAL_TYPE_CANDIDATE = "CANDIDATE"

    private const val TYPE_SIGNAL = "SIGNAL"
    private const val TYPE_FRIEND = "FRIEND"

    private const val FRIEND_TYPE_ADD = "ADD" // 添加好友

    private const val JOIN_ROOM_BROADCAST = "JOIN_ROOM_BROADCAST" // 有新人进入房间后广播
    private const val EXIT_ROOM_BROADCAST = "EXIT_ROOM_BROADCAST" // 有人离开房间后广播


    private const val JOIN_GROUP_BROADCAST = "JOIN_GROUP_BROADCAST" // 有新人进入聊天室广播

    private const val EVENT_SIGNAL_SEND = "signal-send"
    private const val EVENT_SIGNAL_MSG = "signal_msg"

    private val signalEventListenerList = mutableListOf<OnSignalEventListener>()
    private val friendEventListenerList = mutableListOf<OnFriendEventListener>()

    fun init(myid: String, url: String) {
        if (::socket.isInitialized) {
            disconnect()
        }
        this.myid = myid
        val signalSocket = Manager(URI(url)).socket("/signal")
        signalSocket.on(EVENT_SIGNAL_MSG) {
            Log.d(TAG, "收到消息EVENT_SIGNAL_MSG:${it[0]}")
            val response = JSONObject(it[0].toString())
            if (response.getBoolean("success")) {
                val msg = JSONObject(response.getString("msg"))
                val content = msg.getJSONObject("content")
                val fromid = content.getString("fromid")
                val roomid = if (content.has("roomid")) {
                    content.getString("roomid")
                } else {
                    null
                }
                val mediaType = try {
                    when (content.getString("mediaType")) {
                        MediaType.AUDIO.name -> MediaType.AUDIO
                        MediaType.VIDEO.name -> MediaType.VIDEO
                        else -> MediaType.DATA
                    }
                } catch (e: Throwable) {
                    MediaType.DATA
                }
                when (msg.getString("type")) {
                    TYPE_SIGNAL -> {
                        when (val signalType = content.getString("type")) {
                            // 点对点视频通话的协商信令
                            Negotiate.CALL.name,
                            Negotiate.ACK.name,
                            Negotiate.DENY.name,
                            Negotiate.SHUT.name -> {
                                signalEventListenerList.forEach { item ->
                                    // 返回true表示消息被处理，无需继续分发
                                    if (item.onNegotiate(
                                            fromid,
                                            Negotiate.valueOf(signalType)
                                        )
                                    ) return@forEach
                                }
                            }
                            // 多对多视频会议的协商信令
                            RoomNegotiate.ROOM_CALL.name,
                            RoomNegotiate.ROOM_ACK.name,
                            RoomNegotiate.ROOM_DENY.name,
                            RoomNegotiate.ROOM_SHUT.name -> {
                                signalEventListenerList.forEach { item ->
                                    // 返回true表示消息被处理，无需继续分发
                                    if (item.onRoomNegotiate(
                                            fromid,
                                            roomid ?: "",
                                            RoomNegotiate.valueOf(signalType)
                                        )
                                    ) return@forEach
                                }
                            }
                            JOIN_ROOM_BROADCAST -> { // 视频会议加入房间
                                signalEventListenerList.forEach { item ->
                                    // 返回true表示消息被处理，无需继续分发
                                    if (item.onRoomBroadcast(
                                            fromid,
                                            content.getString("newid"),
                                            roomid ?: "",
                                            RoomBroadcastType.JOIN
                                        )
                                    ) return@forEach
                                }
                            }
                            EXIT_ROOM_BROADCAST -> { // 视频会议离开房间
                                signalEventListenerList.forEach { item ->
                                    // 返回true表示消息被处理，无需继续分发
                                    if (item.onRoomBroadcast(
                                            fromid,
                                            content.getString("shutid"),
                                            roomid ?: "",
                                            RoomBroadcastType.EXIT
                                        )
                                    ) return@forEach
                                }
                            }
                            // 群聊的协商信令
                            GroupNegotiate.GROUP_CALL.name,
                            GroupNegotiate.GROUP_ACK.name,
                            GroupNegotiate.GROUP_DENY.name,
                            GroupNegotiate.GROUP_SHUT.name -> {
                                signalEventListenerList.forEach { item ->
                                    // 返回true表示消息被处理，无需继续分发
                                    if (item.onGroupNegotiate(
                                            fromid,
                                            roomid ?: "",
                                            GroupNegotiate.valueOf(signalType)
                                        )
                                    ) return@forEach
                                }
                            }
                            JOIN_GROUP_BROADCAST -> { // 群聊加入房间
                                signalEventListenerList.forEach { item ->
                                    // 返回true表示消息被处理，无需继续分发
                                    if (item.onGroupJoinBroadcast(
                                            fromid,
                                            content.getString("newid"),
                                            roomid ?: ""
                                        )
                                    ) return@forEach
                                }
                            }
                            SIGNAL_TYPE_OFFER -> {
                                val sdp = content.getString("sdp")
                                signalEventListenerList.forEach { item ->
                                    item.onOffer(fromid, sdp, mediaType, roomid)
                                }
                            }
                            SIGNAL_TYPE_ANSWER -> {
                                val sdp = content.getString("sdp")
                                signalEventListenerList.forEach { item ->
                                    item.onAnswer(fromid, sdp, mediaType, roomid)
                                }
                            }
                            SIGNAL_TYPE_CANDIDATE -> {
                                val sdp = content.getString("sdp")
                                val sdpMid = content.getString("sdpMid")
                                val sdpMLineIndex = content.getInt("sdpMLineIndex")
                                val remoteIceCandidate = IceCandidate(sdpMid, sdpMLineIndex, sdp)
                                signalEventListenerList.forEach { item ->
                                    item.onCandidate(fromid, remoteIceCandidate, mediaType, roomid)
                                }
                            }
                        }
                    }
                    TYPE_FRIEND -> {
                        when (content.getString("type")) {
                            FRIEND_TYPE_ADD -> friendEventListenerList.forEach { item ->
                                item.onAddFriend(fromid)
                            }
                        }
                    }
                }
            }
        }.on(Socket.EVENT_CONNECT) {
            Log.d(TAG, "收到消息EVENT_CONNECT:${it}")
        }.on(Socket.EVENT_CONNECTING) {
            Log.d(TAG, "收到消息EVENT_CONNECTING:${it}")
        }.on(Socket.EVENT_CONNECT_ERROR) {
            Log.d(TAG, "收到消息EVENT_CONNECT_ERROR:${it[0]}")
        }.on(Socket.EVENT_CONNECT_TIMEOUT) {
            Log.d(TAG, "收到消息EVENT_CONNECT_TIMEOUT:${it[0]}")
        }
        signalSocket.io().on(Manager.EVENT_TRANSPORT) {
            val transport = it[0] as Transport

            transport.on(Transport.EVENT_REQUEST_HEADERS) { args ->
                Log.d(TAG, "收到消息EVENT_REQUEST_HEADERS:${args[0]}")
                val headers = args[0] as MutableMap<String, List<String>>
                headers["userid"] = listOf(myid)
                headers["nick"] = listOf(myid)
            }
        }
        signalSocket.connect()
        this.socket = signalSocket
    }

    fun getMyId() = myid

    fun addOnSignalEventListener(listener: OnSignalEventListener) {
        signalEventListenerList.add(listener)
    }

    fun removeOnSignalEventListener(listener: OnSignalEventListener) {
        signalEventListenerList.remove(listener)
    }

    fun addOnFriendEventListener(listener: OnFriendEventListener) {
        friendEventListenerList.add(listener)
    }

    fun removeOnFriendEventListener(listener: OnFriendEventListener) {
        friendEventListenerList.remove(listener)
    }

    private fun createJsonObject(toid: String, type: String, content: Map<String, Any>) =
        JSONObject().put("toid", toid)
            .put("type", type)
            .put("content", JSONObject(content).put("fromid", myid))

    fun sendAddFriend(
        toid: String,
        onAck: (Array<Any>) -> Unit = {}
    ) {
        sendMessage(
            toid,
            createJsonObject(toid, TYPE_FRIEND, mapOf("type" to FRIEND_TYPE_ADD)),
            onAck
        )
    }

    /**
     * @param roomid 发送多人视频会议的时候需要加上roomid
     */
    fun sendOffer(
        toid: String,
        sdp: String,
        mediaType: MediaType,
        roomid: String? = null,
        onAck: (Array<Any>) -> Unit = {}
    ) {
        val map = mutableMapOf(
            "type" to SIGNAL_TYPE_OFFER,
            "sdp" to sdp,
            "mediaType" to mediaType.name
        )
        if (roomid != null) {
            map["roomid"] = roomid
        }
        sendMessage(
            toid, createJsonObject(
                toid, TYPE_SIGNAL, map
            ), onAck
        )
    }

    fun sendAnswer(
        toid: String,
        sdp: String,
        mediaType: MediaType,
        roomid: String? = null,
        onAck: (Array<Any>) -> Unit = {}
    ) {
        val map = mutableMapOf(
            "type" to SIGNAL_TYPE_ANSWER,
            "sdp" to sdp,
            "mediaType" to mediaType.name
        )
        if (roomid != null) {
            map["roomid"] = roomid
        }
        sendMessage(toid, createJsonObject(toid, TYPE_SIGNAL, map), onAck)
    }

    fun sendCandidate(
        toid: String,
        sdp: String,
        sdpMid: String,
        sdpMLineIndex: Int,
        mediaType: MediaType,
        roomid: String? = null,
        onAck: (Array<Any>) -> Unit = {}
    ) {
        val map = mutableMapOf(
            "type" to SIGNAL_TYPE_CANDIDATE,
            "sdp" to sdp,
            "sdpMid" to sdpMid,
            "sdpMLineIndex" to sdpMLineIndex,
            "mediaType" to mediaType.name
        )
        if (roomid != null) {
            map["roomid"] = roomid
        }
        sendMessage(toid, createJsonObject(toid, TYPE_SIGNAL, map), onAck)
    }

    fun sendNegotiate(
        toid: String,
        negotiate: Negotiate,
        mediaType: MediaType,
        onAck: (Array<Any>) -> Unit = {}
    ) {
        sendMessage(
            toid, createJsonObject(
                toid, TYPE_SIGNAL, mapOf(
                    "type" to negotiate.name,
                    "mediaType" to mediaType.name
                )
            ), onAck
        )
    }

    fun sendRoomNegotiate(
        toid: String,
        roomid: String,
        negotiate: RoomNegotiate,
        onAck: (Array<Any>) -> Unit = {}
    ) {
        sendMessage(
            toid, createJsonObject(
                toid, TYPE_SIGNAL, mapOf(
                    "type" to negotiate.name,
                    "roomid" to roomid
                )
            ), onAck
        )
    }

    fun sendGroupNegotiate(
        toids: Iterable<String>,
        roomid: String,
        negotiate: GroupNegotiate,
        onAck: (Array<Any>) -> Unit = {}
    ) {
        val toid = toids.joinToString(separator = ",") { it }
        sendGroupNegotiate(toid, roomid, negotiate, onAck)
    }

    fun sendGroupNegotiate(
        toid: String,
        roomid: String,
        negotiate: GroupNegotiate,
        onAck: (Array<Any>) -> Unit = {}
    ) {
        sendMessage(
            toid, createJsonObject(
                toid, TYPE_SIGNAL, mapOf(
                    "type" to negotiate.name,
                    "roomid" to roomid
                )
            ), onAck
        )
    }

    fun sendJoinGroupBroadcast(
        toids: Iterable<String>,
        roomid: String,
        userid: String,
        onAck: (Array<Any>) -> Unit = {}
    ) {
        val toid = toids.joinToString(separator = ",") { it }
        sendMessage(
            toid, createJsonObject(
                toid, TYPE_SIGNAL, mapOf(
                    "type" to JOIN_GROUP_BROADCAST,
                    "roomid" to roomid,
                    "newid" to userid
                )
            ), onAck
        )
    }

    /**
     * 向其他成员发送成员的广播
     * @param userid 用户的id
     * @param roomBroadcastType 发送广播的类型，加入或退出
     */
    fun sendRoomBroadcast(
        toids: Iterable<String>,
        roomid: String,
        userid: String,
        roomBroadcastType: RoomBroadcastType,
        onAck: (Array<Any>) -> Unit = {}
    ) {
        val toid = toids.joinToString(separator = ",") { it }
        if (roomBroadcastType == RoomBroadcastType.JOIN) {
            sendMessage(
                toid, createJsonObject(
                    toid, TYPE_SIGNAL, mapOf(
                        "type" to JOIN_ROOM_BROADCAST,
                        "roomid" to roomid,
                        "newid" to userid
                    )
                ), onAck
            )
        } else if (roomBroadcastType == RoomBroadcastType.EXIT) {
            sendMessage(
                toid, createJsonObject(
                    toid, TYPE_SIGNAL, mapOf(
                        "type" to EXIT_ROOM_BROADCAST,
                        "roomid" to roomid,
                        "shutid" to userid
                    )
                ), onAck
            )
        }
    }


    private fun sendMessage(userid: String, signal: JSONObject, onAck: (Array<Any>) -> Unit) {
        fun doSend() {
            val message = JSONObject()
            message.put("userid", userid)
            message.put("signal", signal.toString())
            Log.i(TAG, "send: $message")
            socket.emit(EVENT_SIGNAL_SEND, message, Ack {
                Log.i(TAG, "ack: ${Arrays.toString(it)}")
                onAck(it)
            })
        }
        if (socket.connected()) {
            doSend()
        } else {
            socket.once(Socket.EVENT_CONNECT) {
                doSend()
            }
            socket.connect()
        }
    }

    fun disconnect() {
        if (::socket.isInitialized) {
            return
        }
        socket.off()
        socket.disconnect()
    }

}